package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisWorker;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * <p>
 *  服务实现类
 * </p>
 */
@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper,
                VoucherOrder> implements IVoucherOrderService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private ISeckillVoucherService iSeckillVoucherService;
    @Resource
    private RedisWorker redisWorker;
    @Resource
    private RedissonClient redissonClient;

    //提前准备一个lua脚本
    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

    //准备一个阻塞队列-->创建好的订单放到阻塞队列
    private  BlockingQueue<VoucherOrder> taskQueue = new
            ArrayBlockingQueue<VoucherOrder>(1024*1024);
    //准备一个线程池-->从阻塞队列里面取出订单,执行下单操作
    private static final ExecutorService servicePool =
            Executors.newSingleThreadExecutor();

    //让线程池一开始就去执行->去阻塞队列中取出订单
    @PostConstruct
    public void init() {
        servicePool.submit(new voucherOrderHandler());
    }

    private IVoucherOrderService proxy;

    //准备一个任务
    private class voucherOrderHandler implements Runnable {
        @Override
        public void run() {
            //取出订单开始执行任务
            while(true) {
                try {
                    //阻塞获取订单
                    VoucherOrder voucherOrder = taskQueue.take();
                    //开启一个异步的线程执行下单操作
                    handleVoucherOrder(voucherOrder);
                } catch (InterruptedException e) {
                    log.error("异常!!!");
                }
            }
        }
    }

    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        //库存充足
        Long userId = UserHolder.getUser().getId();
        //得到锁
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        if(!lock.tryLock()){
            log.error("获取锁失败");
        }
        try{
            //获取与事务有关的对象
            proxy.createVoucher(voucherOrder);
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    /**
     * 实现优惠券秒杀下单功能
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucherOrder(Long voucherId) {
        //使用redis执行lua脚本
        Long userId = UserHolder.getUser().getId();
        Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,
                Collections.emptyList(), voucherId, userId);
        if(result.intValue()!=0) {
            //下单没有成功
            return Result.fail(result == 1 ? "库存不足" : "已经买过一单了");
        }
        //result为0代表下单成功
        //创建一个订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //添加用户id
        voucherOrder.setUserId(userId);
        //添加订单id
        long orderId = redisWorker.nextId("order");
        voucherOrder.setId(orderId);
        //添加秒杀券id
        voucherOrder.setVoucherId(voucherId);
        taskQueue.add(voucherOrder);
        //获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        return Result.ok(orderId);
    }

//    /**
//     * 实现优惠券秒杀下单功能
//     * @param voucherId
//     * @return
//     */
//    @Override
//    public Result seckillVoucherOrder(Long voucherId) {
//        //如果要对该秒杀券进行抢购,首先线获取该秒杀券信息
//        SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId);
//        //1.判断秒杀时间是否尚未开始或者已经结束
//        if(seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
//            //如果开始时间在当前时间的后面,证明秒杀还没有开始
//            return Result.fail("秒杀尚未开始");
//        }
//        if(seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
//            return Result.fail("秒杀已经结束");
//        }
//        //2.判断秒杀券的库存是否充足
//        if(seckillVoucher.getStock() < 1) {
//            //库存不足
//            return Result.fail("库存不足");
//        }
//        //库存充足
//        Long userId = UserHolder.getUser().getId();
//        //SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order:" + userId,stringRedisTemplate);
//        //得到锁
//        RLock lock = redissonClient.getLock("lock:order:" + userId);
//        if(!lock.tryLock()){
//            return Result.fail("不允许重复下单");
//        }
//        try{
//            //获取与事务有关的对象
//            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
//            return proxy.createVoucher(voucherId);
//        }finally {
//            //释放锁
//            lock.unlock();
//        }
//    }
//
    @Transactional
    public void createVoucher(VoucherOrder voucher) {
        //库存充足
        Long userId = voucher.getUserId();
        /**
         * 实现一人一单业务,在减少库存前,先判断一下这个用户有没有购买过这个商品
         */
        int count = query().eq("user_id", userId).
                eq("voucher_id", voucher).count();
        if(count>0) {
            //该用户已经买过了
            log.error("用户已经买过了");
            return;
        }
        //3.可以减少库存了
        boolean isSuccess = iSeckillVoucherService.update().setSql("stock = stock-1")
                .eq("voucher_id", voucher)
                .gt("stock", 0)
                .update();
        //判断是否减少库存成功
        if(!isSuccess) {
            //减少库存失败
            log.error("减少库存失败");
            return;
        }
        save(voucher);
    }
}